package com.spark.market.service.manager;

import com.spark.market.api.dto.ItemInfo;
import com.spark.market.api.dto.SequenceInfo;
import com.spark.market.api.dto.UserInfo;
import com.spark.market.api.mq.ItemStockMqMessage;
import com.spark.market.api.request.ItemDecreaseParam;
import com.spark.market.api.request.OrderCreateParam;
import com.spark.market.api.result.ItemStockDecreaseResult;
import com.spark.market.common.exception.BusinessException;
import com.spark.market.common.exception.EmBusinessError;
import com.spark.market.common.redis.RedisCacheOperate;
import com.spark.market.common.redis.RedisOperateEnum;
import com.spark.market.common.utils.AssertUtils;
import com.spark.market.dal.entity.OrderInfoDo;
import com.spark.market.dal.entity.OrderLogDo;
import com.spark.market.dal.model.OrderModel;
import com.spark.market.dal.repository.OrderRepository;
import com.spark.market.service.*;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Component
public class OrderManager {
    @Resource
    private ItemQueryCoreService itemQueryCoreService;
    @Resource
    private UserQueryCoreService userQueryCoreService;
    @Resource
    private ItemCmdCoreService itemCmdCoreService;
    @Resource
    private OrderRepository orderRepository;
    @Resource
    private SequenceQueryCoreService sequenceQueryCoreService;
    @Resource
    private SequenceCmdCoreService sequenceCmdCoreService;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    /**
     * 风控模型中引入对缓存进行，使用数据库效率低
     *
     * @param itemId
     * @param userId
     * @param amount
     * @param promoId
     * @return
     */
    public ItemInfo verifyOrderStatus(Integer itemId, Integer userId, Integer amount, Integer promoId) {
        //商品校验缓存
        ItemInfo cacheItemInfo = (ItemInfo) redisTemplate.opsForValue().get("item_validate_" + itemId);
        ItemInfo itemDetail = null;
        if (cacheItemInfo == null) {
            itemDetail = itemQueryCoreService.getItemDetail(itemId);
            if (itemDetail == null) {
                throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "商品信息不存在");
            }
            redisTemplate.opsForValue().set("item_validate_" + itemId, itemDetail);
            redisTemplate.expire("item_validate_" + itemId, 10, TimeUnit.MINUTES);
        } else {
            itemDetail = cacheItemInfo;
        }

        //用户校验缓存
        UserInfo cacheUserInfo = (UserInfo) redisTemplate.opsForValue().get("user_validate_" + userId);
        UserInfo userInfo = null;
        if (cacheUserInfo == null) {
            userInfo = userQueryCoreService.queryUserInfo(userId);
            if (userInfo == null) {
                throw new BusinessException(EmBusinessError.USER_NOT_EXIST, "用户不存在");
            }
            redisTemplate.opsForValue().set("user_validate_" + userId, userInfo);
            redisTemplate.expire("user_validate_" + userId, 10, TimeUnit.MINUTES);
        }

        if (amount <= 0 || amount > 99) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "商品数量信息不存在");
        }

        //校验活动信息
        if (promoId != null) {
            //校验对应活动是否存在这个适用商品
            if (promoId.intValue() != itemDetail.getPromoId()) {
                throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "活动信息不正确");
            } else if (itemDetail.getStatus() != 2) { // 校验活动是否正在进行中
                throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "活动尚未开始");
            }
        }
        return itemDetail;
    }


    public void decreaseStock(Integer itemId, Integer amount) {
        AssertUtils.isNotNull(itemId, "itemId is null");
        AssertUtils.isNotNull(amount, "amount is null");
        ItemDecreaseParam itemDecreaseParam = new ItemDecreaseParam();
        itemDecreaseParam.setItemId(itemId);
        itemDecreaseParam.setAmount(amount);
//        ItemStockDecreaseResult itemStockDecreaseResult = itemCmdCoreService.decreaseItemStock(itemDecreaseParam);
        ItemStockDecreaseResult itemStockDecreaseResult = itemCmdCoreService.decreaseItemStockByCache(itemDecreaseParam);
        if (!itemStockDecreaseResult.isStatus()) {
            throw new BusinessException(EmBusinessError.STOCK_NOT_ENOUGH);
        }

    }

    public void saveOrder(OrderModel orderModel) {
        AssertUtils.isNotNull(orderModel, "orderModel is null");
        OrderInfoDo orderInfoDo = convertFromOrderModel(orderModel);
        orderRepository.insertOrder(orderInfoDo);
    }

    private OrderInfoDo convertFromOrderModel(OrderModel orderModel) {
        if (orderModel == null) {
            return null;
        }
        OrderInfoDo orderInfoDo = new OrderInfoDo();
        BeanUtils.copyProperties(orderModel, orderInfoDo);
        orderInfoDo.setOrderPrice(orderModel.getOrderPrice().doubleValue());
        return orderInfoDo;
    }

    //即使主方法失败，也不该回滚id。帮助id的全局唯一性
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public String generateOrderId() {
        //订单号有16位
        StringBuilder stringBuilder = new StringBuilder();
        //前8位为时间信息，年月日
        LocalDateTime now = LocalDateTime.now();
        String nowDate = now.format(DateTimeFormatter.ISO_DATE).replace("-", "");
        stringBuilder.append(nowDate);

        //中间6位为自增序列
        //获取当前Sequence
        SequenceInfo sequenceInfo = sequenceQueryCoreService.querySequenceId("order_info");
        Integer sequence = sequenceInfo.getCurrentValue();
        sequenceInfo.setCurrentValue(sequenceInfo.getCurrentValue() + sequenceInfo.getStep());
        sequenceCmdCoreService.updateSequenceValue(sequenceInfo);
        String sequenceStr = String.valueOf(sequence);
        for (int i = 0; i < 6 - sequenceStr.length(); i++) {
            stringBuilder.append(0);
        }
        stringBuilder.append(sequenceStr);

        //最后2位为分库分表位,暂时写死
        stringBuilder.append("00");

        return stringBuilder.toString();
    }

    public void saveOrderLog(OrderLogDo orderLogDo) {
        orderRepository.insertOrderLog(orderLogDo);
    }

    /**
     * 此处异步扣件数据库的解决思路是，不回滚。会将错误信息进行记录。等待后续人工排查
     *
     * @param param
     * @param itemInfo
     * @return
     */
    @RedisCacheOperate(prefix = "promo_item_stock_", key = "#{param.itemId}", value = "#{param.amount}", method = RedisOperateEnum.ADD)
    @Transactional(rollbackFor = Throwable.class)
    public OrderModel createOrderInTransaction(OrderCreateParam param, ItemInfo itemInfo) {
        //创建订单ID
        String sequenceId = this.generateOrderId();

        //订单入库
        OrderModel orderModel = new OrderModel();
        orderModel.setId(sequenceId);
        if (param.getPromoId() != null && itemInfo.getStatus() != 0) {//更具秒杀状态选择不同金额
            orderModel.setItemPrice(itemInfo.getPromoItemPrice());
            orderModel.setOrderPrice(itemInfo.getPromoItemPrice().multiply(BigDecimal.valueOf(param.getAmount())));
        } else {
            orderModel.setItemPrice(itemInfo.getPrice());
            orderModel.setOrderPrice(itemInfo.getPrice().multiply(BigDecimal.valueOf(param.getAmount())));
        }
        orderModel.setItemId(itemInfo.getId());
        orderModel.setAmount(param.getAmount());
        orderModel.setUserId(param.getUserId());
        orderModel.setPromoId(itemInfo.getPromoId());
        this.saveOrder(orderModel);

        //商品销量增加
        itemCmdCoreService.increaseSales(param.getItemId(), param.getAmount());

        //异步扣件数据库
        ItemStockMqMessage itemStockMqMessage = new ItemStockMqMessage();
        itemStockMqMessage.setItemId(param.getItemId());
        itemStockMqMessage.setAmount(param.getAmount());
        itemStockMqMessage.setOrderId(sequenceId);
        itemStockMqMessage.setUserID(param.getUserId());
        itemCmdCoreService.syncDecreaseItemStockByMq(itemStockMqMessage);
        return orderModel;
    }

    public String queryPromoToken(Integer promoId, Integer amount, Integer itemId, Integer userId) {
        //校验token
        String redisToken = (String) redisTemplate.opsForValue().get("promo_item_" + itemId + "_userId_" + userId);
        if (!StringUtils.isEmpty(redisToken)) {
            return redisToken;
        }
        ItemInfo itemInfo = this.verifyOrderStatus(itemId, userId, amount, promoId);
        //获取秒杀大闸的count数量
        Long result = redisTemplate.opsForValue().increment("promo_door_count_" + promoId, -1);
        if (result < 0) {
            return null;
        }
        //2021-06-0213:04:30
        org.joda.time.format.DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
        StringBuilder startDate = new StringBuilder(itemInfo.getStartDate());
        startDate.insert(10, " ");
        DateTime dateTimeStart = dateTimeFormatter.parseDateTime(startDate.toString());
        StringBuilder endDate = new StringBuilder(itemInfo.getEndDate());
        endDate.insert(10, " ");
        DateTime dateTimeEnd = dateTimeFormatter.parseDateTime(endDate.toString());
        long millis = dateTimeEnd.getMillis() - dateTimeStart.getMillis();
        String token = UUID.randomUUID().toString().replace("-", "");
        redisTemplate.opsForValue().setIfAbsent("promo_item_" + itemId + "_userId_" + userId, token, millis, TimeUnit.MILLISECONDS);
        return token;
    }

    public void verify(OrderCreateParam param) {
        AssertUtils.isNotNull(param.getPromoToken(), "promoToken is null");
        AssertUtils.isNotNull(param.getItemId(),   "itemId is null");
        AssertUtils.isNotNull(param.getUserId(), "userId is null");
        String token = (String)redisTemplate.opsForValue().get("promo_item_" + param.getItemId() + "_userId_" + param.getUserId());
        if (StringUtils.isEmpty(token) || token.equals(param.getPromoToken())){
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
        }
    }
}
